package org.esgl3d.rendering;

import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import org.esgl3d.Mesh;
import org.esgl3d.Mesh.Format;
import org.esgl3d.math.Matrix;
import org.esgl3d.primitives.Pyramid;
import org.esgl3d.rendering.VertexContainer.PrimitiveType;
import org.esgl3d.scene.MeshNode;
import org.esgl3d.scene.Scene;
import org.esgl3d.scene.SceneNode;
import org.esgl3d.util.FpsCounter;
import org.esgl3d.util.HighPerformanceCounter;

import android.opengl.GLSurfaceView;
import android.opengl.GLU;

public class Renderer implements GLSurfaceView.Renderer {
	
	private static Logger logger = Logger.getLogger(Renderer.class.getName());
	
	public enum MatrixType {
		Projection,
		ModelView
	}

	private RenderCaps caps;
	private GL10 gl;
	private SceneNode p;
	
	private RenderQueue queue = new RenderQueue();
	private HighPerformanceCounter timeForRendering = new HighPerformanceCounter("Rendering");
	private TextureManager textureManager = null;
	private Scene sceneToRender = null;
	private final RenderActivity renderActivity;
	private final FpsCounter fpsCounter;
	
	public Renderer(RenderActivity renderActivity) {
		this.renderActivity = renderActivity;
		textureManager = new TextureManager();
		fpsCounter = new FpsCounter();
	}

	public void setScene(Scene value) {
		sceneToRender = value;
	}
	
	public TextureManager getTextureManager() {
		return textureManager;
	}
	
	public FpsCounter getFpsCounter() {
		return fpsCounter;
	}

	@Override
	public void onDrawFrame(GL10 arg0) {
		fpsCounter.next();
		setPerspectiveProjection();
		gl.glClearColor(0, 0, 0, 1);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();                                
        gl.glTranslatef(0.0f,0.0f,-32.0f);					
        
       render(this);
        
        renderActivity.updateScenegraph();
	}

	@Override
	public void onSurfaceChanged(GL10 arg0, int width, int height) {
		  gl.glViewport(0, 0, width, height);                       // Reset The Current Viewport And Perspective Transformation
	     gl.glMatrixMode(GL10.GL_PROJECTION);                           // Select The Projection Matrix
	     gl.glLoadIdentity();                                      // Reset The Projection Matrix
	     GLU.gluPerspective(gl, 45.0f, (float)width / (float)height, 0.1f, 100.0f);  // Calculate The Aspect Ratio Of The Window
	     gl.glMatrixMode(GL10.GL_MODELVIEW);                            // Select The Modelview Matrix
	     gl.glLoadIdentity();                                      // Reset The ModalView Matrix
	}

	@Override
	public void onSurfaceCreated(GL10 arg0, EGLConfig arg1) {
		gl = arg0;
		
		caps = new RenderCaps();
		caps.initialize(gl);
		textureManager.setGl(gl);
				
		//gl.glShadeModel(GL10.GL_SMOOTH);                            //Enables Smooth Color Shading
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);               //This Will Clear The Background Color To Black
        gl.glClearDepthf((float) 1.0);                                  //Enables Clearing Of The Depth Buffer
        gl.glEnable(GL10.GL_DEPTH_TEST);                            //Enables Depth Testing
        gl.glDepthFunc(GL10.GL_LEQUAL);                             //The Type Of Depth Test To Do
        //gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);  // Really Nice Perspective Calculations
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glEnable(GL10.GL_CULL_FACE);
		// which is the front? the one which is drawn counter clockwise
		gl.glFrontFace(GL10.GL_CCW);
		// which one should NOT be drawn
		/*gl.glEnable(GL10.GL_COLOR_MATERIAL);
		gl.glCullFace(GL10.GL_BACK);
		gl.glEnable(GL10.GL_BLEND);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);*/
		
        renderActivity.setupScenegraph();
	}
	
	/**
	 * Creates a vertex container to be used by the application. The renderer
	 * decides whether it is implemented as a vertex array or vbo.
	 * 
	 * @param format Vertex format
	 * @param numberOfVertices The total number of vertices
	 * @return a newly created vertex container
	 */
	public VertexContainer createVertexContainer(VertexFormat format, int numberOfVertices) {
		return createVertexContainer(format, numberOfVertices, VertexContainerOptions.DYNAMIC);
	}
	
	/**
	 * Creates a vertex container to be used by the application. The renderer
	 * decides whether it is implemented as a vertex array or vbo.
	 * 
	 * @param format Vertex format
	 * @param numberOfVertices The total number of vertices
	 * @param options Options to further specify the usage
	 * @return a newly created vertex container
	 */
	public VertexContainer createVertexContainer(VertexFormat format, int numberOfVertices, VertexContainerOptions options) {
		
					
		if (!caps.isVboSupported() || options.isVertexArrayForced()) {
			if (logger.isLoggable(Level.FINE))
				logger.fine( String.format("Using Vertex Array (%d vertices)", numberOfVertices));
			return new VertexArray(numberOfVertices, format, gl);
		}
		else {
			if (logger.isLoggable(Level.FINE))
				logger.fine( String.format("Using Vertex Buffer Object (%d vertices)", numberOfVertices));
			//return new VertexArray(numberOfVertices, format, gl);
			return new VertexBuffer(numberOfVertices, format, (GL11)gl);
		}
			
	}
	
	public void activeMatrixModel(MatrixType type) {
		if (type == MatrixType.ModelView)
			gl.glMatrixMode(GL10.GL_MODELVIEW);
		else
			gl.glMatrixMode(GL10.GL_PROJECTION);
	}
	
	public void loadMatrix(Matrix m) {
		gl.glLoadMatrixf(m.buffer);
	}
	
	public void pushMatrix() {
		gl.glPushMatrix();
	}
	
	public void popMatrix() {
		gl.glPopMatrix();
	}
	
	public void mulMatrix(Matrix m) {
		gl.glMultMatrixf(m.buffer);
	}
	
	public void setPerspectiveProjection() {
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();      
		GLU.gluPerspective(gl, 45, 320.0f/480.0f, 0.1f, 1000);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();      
	}
	
	public void setOrthographicProjection() {
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();   
		GLU.gluOrtho2D(gl, 0, 20, 30, 0);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();
	}
	
	private int currentlyBoundTexture = 0;
	private boolean texturingEnabled = false;
	
	/**
	 * Sets the appropriate opengl states for textures
	 * 
	 * @param toBind texture to bind or null to unbind
	 */
	public void bindTexture(Texture toBind) {
		if (toBind != null) {
			if (!texturingEnabled) {
				gl.glEnable(GL10.GL_TEXTURE_2D);
				texturingEnabled = true;
			}
			if (currentlyBoundTexture != toBind.getTextureId()) {
				gl.glBindTexture(GL10.GL_TEXTURE_2D, toBind.getTextureId());
				currentlyBoundTexture = toBind.getTextureId();
			}
		} else {
			if (texturingEnabled) {
				gl.glDisable(GL10.GL_TEXTURE_2D);
				gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
				currentlyBoundTexture = 0;
				texturingEnabled = false;
			}
		}
	}
	
	public GL10 getGl() {
		return gl;
	}
	
	public void render(Renderer renderer) {
		timeForRendering.start();
				
		queue.reset();
				
		if (sceneToRender != null)
			sceneToRender.getSpatialStructure().getVisibleNodes(null, queue);
				
		
		renderer.activeMatrixModel(MatrixType.ModelView);
		for (SceneNode curNode : queue.getNodesToRender()) {
			if (curNode instanceof MeshNode) {
				MeshNode cur = (MeshNode)curNode;
				Mesh geometry = cur.getGeometry();
				
				int count = cur.getGeometry().getNumberOfVertices();
				bindTexture(geometry.getTexture());
							
				renderer.pushMatrix();
				renderer.mulMatrix(cur.getWorldTransformation());
				
				Format format = geometry.getFormat();
			
				if (format == Format.Triangle) {
					geometry.getVertices().draw(PrimitiveType.Triangle, 0, count);
				} else {
					geometry.getVertices().draw(PrimitiveType.TriangleStrip, 0, count);
				}
				renderer.popMatrix();
			}
		}
		timeForRendering.stop();
		
		if (timeForRendering.getCounterDurationMillis() > 5000) {
			if (logger.isLoggable(Level.INFO))
				logger.info(timeForRendering.getStatisticString());
			timeForRendering.reset();
		}
	}
	
	
	
	

}
